//
//  NSDataAdditions.m
//  MailDelivery
//
//  Created by Dante Palacios on 26/08/09.
//  Copyright 2009 Dante Palacios. All rights reserved.
//


#import "NSDataAdditions.h"

#import <openssl/evp.h>
#import <openssl/bio.h>

#import <CoreFoundation/CoreFoundation.h>

#import <Security/CMSDecoder.h>
#import <Security/CMSEncoder.h>


static const char *hexDigit = "0123456789ABCDEF";

@implementation NSData (Base64Additions)

- (NSString *)encodeBase64 
{
    return [self encodeBase64WithNewlines:NO];
}

- (NSString *)encodeBase64WithNewlines:(BOOL)encodeWithNewlines
{
    // Create a memory buffer which will contain the Base64 encoded string
    BIO *mem = BIO_new(BIO_s_mem());
    
    // Push on a Base64 filter so that writing to the buffer encodes the data
    BIO * b64 = BIO_new(BIO_f_base64());
    if (!encodeWithNewlines)
        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    mem = BIO_push(b64, mem);
    
    // Encode all the data
    BIO_write(mem, [self bytes], (int)[self length]);
    (void)BIO_flush(mem);
    
    // Create a new string from the data in the memory buffer
    char * base64Pointer;
    long base64Length = BIO_get_mem_data(mem, &base64Pointer);
    NSString * base64String = [[[NSString alloc] initWithBytes:base64Pointer length:base64Length encoding:NSASCIIStringEncoding] autorelease];//[NSString stringWithCString:base64Pointer length:base64Length];
	
    // Clean up and go home
    BIO_free_all(mem);
    return base64String;
}

@end

@implementation NSData(MailDeliveryQuotedPrintableAdditions)

- (NSData *)encodeQuotedPrintableWithLineLength:(NSInteger)lineLength inHeader:(BOOL)flag 
{
	NSMutableData *data = [[NSMutableData alloc] initWithCapacity:[self length]];
	const unsigned char *b = [self bytes];
	NSInteger length = [self length];
	
	char buf[4];
	buf[3] = 0;
	buf[0] = '=';
	
	NSInteger i, line = 0;
	for (i = 0; i < length; i++, b++) {
		if (lineLength && line >= lineLength) {
			[data appendBytes: "=\n" length:2];
			line = 0;
		}
		// RFC says must encode space and tab right before end of line
		if ( (*b == ' ' || *b == '\t') && i < length - 1 && b[1] == '\n') {
			buf[1] = hexDigit[(*b)>>4];
			buf[2] = hexDigit[(*b)&15];
			[data appendBytes: buf 
							   length: 3];
			line += 3;
		}
		// FIXME: really always pass \n through here?
		else if (!flag &&
				 (*b == '\n' || *b == ' ' || *b == '\t'
				  || (*b >= 33 && *b <= 60)
				  || (*b >= 62 && *b <= 126))) {
			[data appendBytes: b  length: 1];
			if (*b == '\n') {
				line = 0;
			}else {
				line++;
			}
		}else if (flag && ((*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z'))) {
			[data appendBytes: b  length: 1];
			if (*b == '\n') {
				line = 0;
			}else {
				line++;
			}
		}else if (flag && *b == ' ') {
			[data appendBytes: "_"  length: 1];
		}else {
			buf[1] = hexDigit[(*b)>>4];
			buf[2] = hexDigit[(*b)&15];
			[data appendBytes: buf  length: 3];
			line += 3;
		}
    }
	
	return [data autorelease];
}

@end

@implementation NSData (CMSAddtions)

- (NSData *)encodedCMSDataUsingCertificateRef:(SecCertificateRef)certificateRef SMIMESigned:(BOOL)SMIMESigned SMIMEEncripted:(BOOL)SMIMEEncripted 
{
	NSData *data = nil;
	CMSEncoderRef cmsEncoder = nil;
	CMSEncoderCreate(&cmsEncoder);
	if (cmsEncoder) 
	{
		const void *content =  (const void *) [self bytes];
		size_t contentLen = (size_t) [self length];
		
		if (SMIMEEncripted) 
		{
			CMSEncoderUpdateContent(cmsEncoder, content, contentLen);
			CMSEncoderAddRecipients(cmsEncoder, certificateRef);
		}
		
		SecIdentityRef identityRef = nil;
		if (SMIMESigned) 
		{
			SecIdentityCreateWithCertificate(nil, certificateRef, &identityRef);
			if (identityRef) 
			{
				CMSEncoderUpdateContent(cmsEncoder, content, contentLen);
				CMSEncoderAddSigners(cmsEncoder, identityRef);
				if (!SMIMEEncripted) 
				{
					CMSEncoderUpdateContent(cmsEncoder, content, contentLen);
					CMSEncoderSetHasDetachedContent(cmsEncoder, YES);
				}
				CFRelease(identityRef);
			}
		}
		
		CMSEncoderUpdateContent(cmsEncoder, content, contentLen);
		CMSEncoderAddSignedAttributes(cmsEncoder, kCMSAttrSmimeCapabilities);
		
		CFDataRef encryptedDataRef = nil;
		CMSEncoderCopyEncodedContent(cmsEncoder, &encryptedDataRef);
		if (encryptedDataRef) 
		{
			data = [NSData dataWithData:(NSData *) encryptedDataRef];
		}
		
		CFRelease(cmsEncoder);
	}		
	
	return data;
}

- (NSData *)encodedCMSDataUsingCertificateRef:(SecCertificateRef)certificateRef 
{
	return [self encodedCMSDataUsingCertificateRef:certificateRef SMIMESigned:YES SMIMEEncripted:YES];
}

- (NSData *)decodedCMSData 
{
	NSData *data = nil;
	CMSDecoderRef cmsDecoder = nil;
	CMSDecoderCreate(&cmsDecoder);
	if (cmsDecoder) 
	{
		CMSDecoderUpdateMessage(cmsDecoder, (const void *) [self bytes], (size_t) [self length]);
		CMSDecoderFinalizeMessage(cmsDecoder);
		
		CFDataRef dataRef = nil;
		CMSDecoderCopyContent(cmsDecoder, &dataRef);
		if (dataRef) 
		{
			data = [NSData dataWithData:(NSData *) dataRef];
			CFRelease(dataRef);
		}
		CFRelease(cmsDecoder);
	}
	
	return data;
}

@end